package ssau.knyazev.mmoi.module;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;

import javax.imageio.ImageIO;

import ssau.knyazev.core.Complex;
import ssau.knyazev.core.ComplexMath;
import ssau.knyazev.core.image.ComplexImageModel;
import ssau.knyazev.core.image.ImageModel;
import ssau.knyazev.fft.modules.oob.FFTModule;
import ssau.knyazev.mmoi.ModelSettings;
import ssau.knyazev.modules.Helper;

public class Processor {

	public static void blur(String inImg, String outImg) throws FileNotFoundException, IOException{
		BufferedImage bi = ImageIO.read(new FileInputStream(inImg + ".BMP"));
		ImageModel image = new ImageModel(bi);
		float[][] arr = image.getValues();
		float[][] res = new float[arr.length][arr[0].length];
		for (int i = 1; i < arr.length-1; i++){
			for (int j = 1; j < arr.length-1; j++){
				res[i][j] = (arr[i-1][j-1] + 2*arr[i-1][j] + arr[i-1][j+1] + 
							2*arr[i][j-1] + 4*arr[i][j] + 2*arr[i][j+1] +
							arr[i+1][j-1] + 2*arr[i+1][j] + arr[i+1][j+1])/20;
			}
		}
		image.setValues(res);
		ImageIO.write(image.getBufferedImage(), "BMP", new FileOutputStream(outImg + ".BMP"));
	}
	
	public static void inverse(String inImg, String outImg) throws FileNotFoundException, IOException{
		BufferedImage bi = ImageIO.read(new FileInputStream(inImg + ".BMP"));
		ImageModel image = new ImageModel(bi);
		float[][] arr = image.getValues();
		for (int i = 0; i < arr.length; i++){
			for (int j = 0; j < arr.length; j++){
				arr[i][j] = 255-arr[i][j];
			}
		}
		image.setValues(arr);
		ImageIO.write(image.getBufferedImage(), "BMP", new FileOutputStream(outImg + ".BMP"));
	}
	
	public static void threshold(String inImg, String outImg, double threshold) throws FileNotFoundException, IOException{
		BufferedImage bi = ImageIO.read(new FileInputStream(inImg + ".BMP"));
		ImageModel image = new ImageModel(bi);
		double max = 0;
		float[][] arr = image.getValues();
		for (int i = 0; i < arr.length; i++){
			for (int j = 0; j < arr.length; j++){
				max = (max > arr[i][j]) ? max : arr[i][j];
				arr[i][j] = (arr[i][j] > threshold) ? 255 : 0;
			}
		}
		System.out.println(max);
		image.setValues(arr);
		ImageIO.write(image.getBufferedImage(), "BMP", new FileOutputStream(outImg + ".BMP"));
	}
	
	public static void median(String inImg, String outImg, int index) throws FileNotFoundException, IOException{
		BufferedImage bi = ImageIO.read(new FileInputStream(inImg + ".BMP"));
		ImageModel image = new ImageModel(bi);
		float[][] arr = image.getValues();
		float[][] res = new float[arr.length+2][arr.length+2]; 
		for (int i = 0; i < arr.length; i++){
			for (int j = 0; j < arr[0].length; j++){
				res[i+1][j+1] = arr[i][j];
			}
		}
		for (int i = 0; i < arr.length; i++){
			for (int j = 0; j < arr[0].length; j++){
				float[] sample = new float[9];
				int k = 0;
				for (int i1 = -1; i1 <= 1; i1++){
					for (int j1 = -1; j1 < 1; j1++){
						sample[k] = res[i+i1+1][j+j1+1];
						k++;
					}
				}
				Arrays.sort(sample);
				arr[i][j] = sample[index];
			}
		}
		fill(arr);
		image.setValues(arr);
		ImageIO.write(image.getBufferedImage(), "BMP", new FileOutputStream(outImg + ".BMP"));
	}
	
	public static void clean2(String inImg, String outImg, int window, int index) throws FileNotFoundException, IOException{
		BufferedImage bi = ImageIO.read(new FileInputStream(inImg + ".BMP"));
		ImageModel image = new ImageModel(bi);
		float[][] arr = image.getValues();
		float[][] res = new float[arr.length+window-1][arr.length+window-1]; 
		for (int i = 0; i < arr.length; i++){
			for (int j = 0; j < arr[0].length; j++){
				res[i+window/2][j+window/2] = arr[i][j];
			}
		}
		for (int i = 0; i < arr.length; i++){
			for (int j = 0; j < arr[0].length; j++){
				double sum = 0;
				for (int i1 = -window/2; i1 <= window/2; i1++){
					for (int j1 = -window/2; j1 < window/2; j1++){
						sum = sum + res[i+i1+window/2][j+j1+window/2];
					}
				}
				arr[i][j] = (sum > 255*index) ? arr[i][j] : 0; 
			}
		}
		image.setValues(arr);
		ImageIO.write(image.getBufferedImage(), "BMP", new FileOutputStream(outImg + ".BMP"));
	}
	
	public static void wiener(String inImg, String outImg) throws Exception{
		BufferedImage bi = ImageIO.read(new FileInputStream("01 Source Image" + ".BMP"));
		ImageModel image = new ImageModel(bi);
		ComplexImageModel Sx = new ComplexImageModel(Helper.transformFloat(image.getValues()));
		/*
		Helper.inverse(Sx.getValues());
		CalculationCore.getInst().fft2d(Sx.getValues());
		*/
		FFTModule.fft2D(Sx.getValues());
		ComplexImageModel H = Sx.clone();
		H = ObserveModule.opticH(H);
		ComplexImageModel H_ = H.clone();
		for (Complex[] str: H_.getValues()){
			for (Complex val: str){
				val = val.linked();
			}
		}
		
		ComplexImageModel fst = new ComplexImageModel(ComplexMath.elementMatrixMultiplication(H_.getValues(), Sx.getValues()));
		ComplexImageModel snd = new ComplexImageModel(ComplexMath.elementMatrixMultiplication(H.getValues(), H_.getValues()));
		snd.setValues(ComplexMath.elementMatrixMultiplication(snd.getValues(), Sx.getValues()));
		increaseMatrBy(snd, ModelSettings.sigma2*ModelSettings.sigma2);
		
		ComplexImageModel G = new ComplexImageModel(ComplexMath.elementMatrixDevision(fst.getValues(), snd.getValues()));
		bi = ImageIO.read(new FileInputStream(inImg + ".BMP"));
		image = new ImageModel(bi);
		ComplexImageModel Y = new ComplexImageModel(Helper.transformFloat(image.getValues()));
		/*
		Helper.inverse(Y.getValues());
		CalculationCore.getInst().fft2d(Y.getValues());
		*/
		FFTModule.fft2D(Y.getValues());
		ComplexImageModel rcv = new ComplexImageModel(ComplexMath.elementMatrixMultiplication(G.getValues(), Y.getValues()));
		/*
		Helper.inverse(rcv.getValues());
		CalculationCore.getInst().inverseFFT2D(rcv.getValues());	
		*/
		FFTModule.inverseFFT2D(rcv.getValues());
		ImageIO.write(rcv.getImageModel(false).getBufferedImage(), "BMP", new FileOutputStream(outImg + ".BMP"));
	}
	
	private static void increaseMatrBy(ComplexImageModel cim, double x){
		Complex[][] arr = cim.getValues();
		for (int i = 0; i < arr.length; i++){
			for (int j = 0; j < arr[0].length; j++){
				arr[i][j] = arr[i][j].incBy(0.00000001);
			}
		}
		cim.setValues(arr);
	}
	
	public static void errors(String[] files, String outName) throws IOException{
		double val1 = 0;
		double val2 = 0;
		double sko = 0;
		double max = 0;
		double tmp = 0;
		int n = 0;
		
		StringBuilder sb = new StringBuilder("Function#1\r\n");
		String fst = files[0];
		String snd = files[1];
		Scanner scan1 = new Scanner(new File(fst));
		Scanner scan2 = new Scanner(new File(snd));
		while (scan1.hasNext()){
			scan1.next();
			val1 = Double.parseDouble(scan1.next());
			scan2.next();
			val2 = Double.parseDouble(scan2.next());
			tmp = Math.abs(val2 - val1);
			max = (tmp > max) ? tmp : max;
			sko = sko + tmp*tmp;
			n++;
		}
		sko = sko/(n*n);
		sb.append("MAX ERR = " + max);
		sb.append(" SKO = " + sko);
		sb.append("\r\n");
		
		sb.append("Function#2\r\n");
		fst = files[2];
		snd = files[3];
		scan1 = new Scanner(new File(fst));
		scan2 = new Scanner(new File(snd));
		while (scan1.hasNext()){
			scan1.next();
			val1 = Double.parseDouble(scan1.next());
			scan2.next();
			val2 = Double.parseDouble(scan2.next());
			tmp = Math.abs(val2 - val1);
			max = (tmp > max) ? tmp : max;
			sko = sko + tmp*tmp;
		}
		sko = sko/(n*n);
		sb.append("MAX ERR = " + max);
		sb.append(" SKO = " + sko);
		sb.append("\r\n");
		
		sb.append("Function#3\r\n");
		fst = files[2];
		snd = files[3];
		scan1 = new Scanner(new File(fst));
		scan2 = new Scanner(new File(snd));
		sko = 0;
		max = 0;
		while (scan1.hasNext()){
			scan1.next();
			val1 = Double.parseDouble(scan1.next());
			scan2.next();
			val2 = Double.parseDouble(scan2.next());
			tmp = Math.abs(val2 - val1);
			max = (tmp > max) ? tmp : max;
			sko = sko + tmp*tmp;
		}
		sko = sko/(n*n);
		sb.append("MAX ERR = " + max);
		sb.append(" SKO = " + sko);
		sb.append("\r\n");
		
		FileWriter fw = new FileWriter(outName);
		fw.write(sb.toString());
		fw.close();
	}
	
	public static double sko(String file1, String file2) throws FileNotFoundException, IOException{
		BufferedImage bi = ImageIO.read(new FileInputStream(file1 + ".BMP"));
		float[][] image1 = (new ImageModel(bi)).getValues();
		bi = ImageIO.read(new FileInputStream(file2 + ".BMP"));
		float[][] image2 = (new ImageModel(bi)).getValues();
		double sko = 0;
		double val = 0;
		for (int i = 0; i < image1.length; i++){
			for (int j = 0; j < image1[0].length; j++){
				val = Math.abs(image1[i][j] - image2[i][j]);
				sko = sko + val*val;
			}
		}
		sko = sko/(image1.length*image1[0].length);
		return sko;
	}
	
	public static void findRoots(ImageModel img) throws FileNotFoundException, IOException{
		float[][] arr = img.getValues();
		double sum = 0;
		int prevI = 1;
		Map<Integer, double[]> map = new HashMap<Integer, double[]>();
		for (int i = 1; i < arr.length-1; i++){
			sum = 0;
			for (int k = -1; k <= 1; k++){
				for (int l = -1; l <= 1; l++){
					sum = sum + arr[i+k][arr[0].length-2 + l];
				}
			}
			if (sum == 0){
				prevI = i+1;
			}
			if (sum != 0){
				double[] tarr = (map.get(prevI) == null) ? new double[2] : map.get(prevI);
				double t = tarr[0];
				double l = tarr[1] + 1;
				sum = sum + t;
				tarr[0] = sum;
				tarr[1] = l;
				map.put(prevI, tarr);
			}
		}
		Map<Double, Double> map2 = new HashMap<Double, Double>();
		for (Integer x: map.keySet()){
			double[] tarr = map.get(x);
			double newKey = x + tarr[1]/2;
			map2.put(tarr[0], newKey);
			System.out.println("key = " + x);
			System.out.println("lenght = " + tarr[1]);
			System.out.println("value = " + tarr[0]);
		}
		Object[] arr2 = map2.keySet().toArray();
		Arrays.sort(arr2);
		for (Object l : arr2){
			System.out.println(l);
		}
		Object fst = arr2[arr2.length-1];
		Object snd = arr2[arr2.length-2];
		Object trd = arr2[arr2.length-3];
		int start1 = (int)map2.get(fst).doubleValue();
		int start2 = (int)map2.get(snd).doubleValue();
		int start3 = (int)map2.get(trd).doubleValue();
		System.out.println(start1 + "||"  + start2+ "||" + start3);
		
		BufferedImage bi = new BufferedImage(img.getValues().length, img.getValues()[0].length, BufferedImage.TYPE_INT_RGB);
		List<int[]> points = getCep(start1, img);
		paint(points, bi);
		
		points = getCep(start2, img);
		paint(points, bi);
		
		points = getCep(start3, img);
		paint(points, bi);
		
		ImageIO.write(bi, "BMP", new FileOutputStream("15 Finded" + ".BMP"));

	}
	
	public static List<int[]> getCep(int start, ImageModel image){
		List<int[]> res = new LinkedList<int[]>();
		float[][] img = image.getValues();
		int med = start;
		int count = 1;
		int j = start+1;
		while (img[j][img[0].length-1] != 0){
			med = med + j;
			count++;
			j++;
		}
		j = start-1;
		while (img[j][img[0].length-1] != 0){
			med = med + j;
			count++;
			j--;
		}
		med = med/count;
		int[] q = {img[0].length-1, med};
		res.add(q);
		int lastX = med;
		int lastY = img[0].length-1;
		for (int t = img[0].length-2; t >= 0; t--){
			med = defineMed(lastX, t, img);
			if (med != -1){
				int d = Math.abs(1) + Math.abs(med - lastX);
				int x = nearPoint(lastX, lastY, t, d, img);
				q= new int[2];
				q[0] = t;
				q[1] = x;
				res.add(q);
				lastX = x;
				lastY = t;
			}
		}
		return res;
	}
	
	private static int defineMed(int x, int y, float[][] img){
		int differ = 8;
		int res = -1;
		int sum = 0;
		int count = 0;
		for (int i = -differ; i <= differ; i++){
			double t = 0;
			try{
				t = img[x+i][y];
			} catch (ArrayIndexOutOfBoundsException e){}
			if (t != 0){
				sum = sum + x + i;
				count++;
			}
		}
		res = (count != 0) ? sum/count : res;
		return res;
	}
	
	private static int nearPoint(int lastX, int lastY, int y, int d, float[][] img){
		int mindif = 1000;
		int minX = -1;
		int differ = 8;
		for (int i = -differ; i <= differ; i++){
			double t = 0;
			try{
				t = img[lastX+i][y];
			} catch (ArrayIndexOutOfBoundsException e){}
			if (t != 0){
				int dif = (Math.abs(lastY - y)) + (Math.abs(i));
				dif = Math.abs(dif - d);
				if (dif < mindif){
					mindif = dif;
					minX = lastX + i;
				}
			}
		}
		return minX;
	}
	
	private static void paint(List<int[]> points, BufferedImage bi) throws FileNotFoundException, IOException{
		Graphics g = bi.getGraphics();
		g.setColor(Color.WHITE);
		Iterator<int[]> it = points.iterator();
		int[] prev = it.next();
		int[] cur = prev;
		while(it.hasNext()){
			cur = it.next();
			g.drawLine(prev[1], prev[0], cur[1], cur[0]);
			prev = cur;
		}
		g.drawLine(prev[1], prev[0], cur[1], 0);
	}
	
	public static void compare(String in1, String in2) throws FileNotFoundException, IOException{
		BufferedImage bi = ImageIO.read(new FileInputStream(in1 + ".BMP"));
		ImageModel image1 = new ImageModel(bi);
		bi = ImageIO.read(new FileInputStream(in2 + ".BMP"));
		ImageModel image2 = new ImageModel(bi);
		double f1 = 0;
		double f2 = 0;
		System.out.println("FIRST FUNCTION COMPARING...");
		double maxErr = 0;
		double sko = 0;
		for (int i = 0; i < image1.getValues()[0].length;i++){
			f1 = findMed(image1.getValues(), i, 1);
			f2 = findMed(image2.getValues(), i, 1);
			double dif = Math.abs(f1 - f2);
			sko = sko + dif*dif;
			maxErr = (maxErr <= dif) ? dif : maxErr;
		}
		sko = sko/(image1.getValues().length*image1.getValues()[0].length);
		System.out.println("Max error = " + maxErr);
		System.out.println("SKO = " + String.format("%.5f", sko));
		System.out.println("SECOND FUNCTION COMPARING...");
		maxErr = 0;
		sko = 0;
		for (int i = 0; i < image1.getValues()[0].length;i++){
			f1 = findMed(image1.getValues(), i, 2);
			f2 = findMed(image2.getValues(), i, 2);
			double dif = Math.abs(f1 - f2);
			sko = sko + dif*dif;
			maxErr = (maxErr <= dif) ? dif : maxErr;
		}
		sko = sko/(image1.getValues().length*image1.getValues()[0].length);
		System.out.println("Max error = " + maxErr);
		System.out.println("SKO = " + String.format("%.5f", sko));
		System.out.println("THIRD FUNCTION COMPARING...");
		maxErr = 0;
		sko = 0;
		for (int i = 0; i < image1.getValues()[0].length;i++){
			f1 = findMed(image1.getValues(), i, 3);
			f2 = findMed(image2.getValues(), i, 3);
			double dif = Math.abs(f1 - f2);
			sko = sko + dif*dif;
			maxErr = (maxErr <= dif) ? dif : maxErr;
		}
		sko = sko/(image1.getValues().length*image1.getValues()[0].length);
		System.out.println("Max error = " + maxErr);
		System.out.println("SKO = " + String.format("%.5f", sko));
	}
	
	private static double findMed(float[][] img, int y, int num){
		double med = 0;
		int k = 0;
		int start = 0;
		int end = 0;
		int curBlock = 1;
		while (curBlock <= num){
			while (img[k][y] == 0){
				k++;
			}
			curBlock++;
			start = k;
			while (img[k][y] != 0){
				k++;
			}
			end = k-1;
		}
		med = (start + end)/2;
		return med;
	}
	
	private static void fill(float[][] arr){
		arr[arr.length/2 + 21][arr[0].length-1] = 255;
		arr[arr.length/2 + 20][arr[0].length-1] = 255;
		arr[arr.length/2 + 19][arr[0].length-1] = 255;
		arr[arr.length/2 + 21][arr[0].length-2] = 255;
		arr[arr.length/2 + 20][arr[0].length-2] = 255;
		arr[arr.length/2 + 21][arr[0].length-3] = 255;
		arr[arr.length/2 + 20][arr[0].length-3] = 255;
		
		arr[arr.length/2 + 61][arr[0].length-1] = 255;
		arr[arr.length/2 + 60][arr[0].length-1] = 255;
		arr[arr.length/2 + 59][arr[0].length-1] = 255;
		arr[arr.length/2 + 61][arr[0].length-2] = 255;
		arr[arr.length/2 + 60][arr[0].length-2] = 255;
		arr[arr.length/2 + 61][arr[0].length-3] = 255;
		arr[arr.length/2 + 60][arr[0].length-3] = 255;

		arr[arr.length/2 + 101][arr[0].length-1] = 255;
		arr[arr.length/2 + 100][arr[0].length-1] = 255;
		arr[arr.length/2 + 99][arr[0].length-1] = 255;
		arr[arr.length/2 + 101][arr[0].length-2] = 255;
		arr[arr.length/2 + 100][arr[0].length-2] = 255;
		arr[arr.length/2 + 101][arr[0].length-3] = 255;
		arr[arr.length/2 + 100][arr[0].length-3] = 255;
	}
	
}
